﻿//
// $Id$

package {

import flash.events.Event;
import flash.events.TimerEvent;

import com.threerings.util.Log;
import com.threerings.util.Map;
import com.threerings.util.Maps;
import com.threerings.util.Random;

import com.whirled.ControlEvent;
import com.whirled.EntityControl;
import com.whirled.PetControl;

/**
 * Manages a Pet's mental state.
 */
public class FollowBrain2
{
    /** Use this to log things. */
    public static var log :Log = Log.getLog(FollowBrain2);

    /** Used to enable debugging feedback. */
    public static var debug :Boolean = false;

    /**
     * Creates a brain that will use the supplied control to interact with the Whirled and will
     * control the supplied body.
     */
    public function FollowBrain2 (ctrl :PetControl, body :Body, distance :Number = 150)
    {
        _ctrl = ctrl;
        _body = body;

        _ctrl.addEventListener(ControlEvent.STATE_CHANGED, stateChanged);
        _ctrl.addEventListener(TimerEvent.TIMER, tick);
        _ctrl.setTickInterval(3000);
        _ctrl.addEventListener(Event.UNLOAD, function (event :Event) :void {
            shutdown();
        });

        // determine which states are available to us by virtue of the body having an idle
        // animation for them (and ideally a transition to and from content)
        for each (var state :State in State.enumerateStates()) {
            if (_body.supportsState(state.name)) {
                debugMessage("I can do '" + state.name + "'.");
                _states.put(state.name, state);
            }
        }

        // start in our current state
        stateChanged(null);
		
		var envir :String = _ctrl.getEnvironment();
		if (envir == EntityControl.ENV_VIEWER || envir == EntityControl.ENV_SHOP) {
			_ownerId = _ctrl.getInstanceId();
		} else {
			_ownerId = _ctrl.getOwnerId();
		}
		_dist = distance;

        _ctrl.addEventListener(ControlEvent.ENTITY_MOVED, handleMovement);
		
		moveTo(_ctrl.getEntityProperty(EntityControl.PROP_LOCATION_PIXEL, getEntityId(_ownerId)) as Array);
    }

    /**
     * Switches our pet to the specified state.
     */
    public function switchToState (state :State) :void
    {
        if (!_states.containsKey(state.name)) {
            log.warning("Requested to switch to unsupported state " + state + ".");
            state = State.CONTENT; // fall back to contented
        }
        debugMessage("I'm switching to '" + state.name + "'.");
        _ctrl.setState(state.name);
    }

    /**
     * Cleans up after our brain, unregistering listeners, etc.
     */
    public function shutdown () :void
    {
        // nada for now
    }

    protected function stateChanged (event :ControlEvent) :void
    {
        _state = State.getState(_ctrl.getState());
        _body.switchToState(_state.name);
    }

    protected function tick (event :TimerEvent) :void
    {
        // don't make any state changes while we're moving or transitioning between states
        if (_ctrl.isMoving() || _body.inTransition()) {
            log.info("Not thinking [moving=" + _ctrl.isMoving() +
                     ", trans=" + _body.inTransition() + "].");
            return;
        }

        // 10% chance of changing state
        if (_rando.nextInt(100) > 90) {
            switchToState(selectNewState());
            return;
        }
    }

    protected function selectNewState () :State
    {
        var avail :Array = new Array();
        for each (var state :State in _state.transitions) {
            if (_states.containsKey(state.name)) {
                avail.push(state);
            }
        }
        if (avail.length == 0) {
            log.warning("Zoiks! Cannot transition out of " + _state + "!");
            return State.CONTENT;
        }
        return (avail[_rando.nextInt(avail.length)] as State);
    }

    protected function debugMessage (message :String) :void
    {
        if (debug && _ctrl.isConnected()) {
            _ctrl.sendChat(message);
        } else {
            log.info(message);
        }
    }
	
	/**
	 * Returns the entity ID of a user. Checks through all the users in the room for the one
	 * whose member ID matches the one given.
	 */
	protected function getEntityId (memberId :int) :String
	{
		var allIds:Array = _ctrl.getEntityIds(EntityControl.TYPE_AVATAR);
		for each (var id :String in allIds) {
			if (memberId == int(_ctrl.getEntityProperty(EntityControl.PROP_MEMBER_ID, id))) {
				return id;
			}
		}
		return null;
	}
	
	/**
	 * Listens for user movement and follows the user if it is the pet's owner.
	 */
	protected function handleMovement (event :ControlEvent = null) :void
	{
		var targetId :String = event.name;
		if (int(_ctrl.getEntityProperty(EntityControl.PROP_MEMBER_ID, targetId)) == _ownerId && _ctrl.getEntityProperty(EntityControl.PROP_TYPE, targetId) == EntityControl.TYPE_AVATAR) {
			if (event.value != null) {
				var roomBounds :Array = _ctrl.getRoomBounds();
				var eventArray :Array = event.value as Array;
				moveTo([eventArray[0] * roomBounds[0], eventArray[1] * roomBounds[1], eventArray[2] * roomBounds[2]]);
			} else {
				moveTo(_ctrl.getEntityProperty(EntityControl.PROP_LOCATION_PIXEL, targetId) as Array);
			}
		}
	}
	
	/**
	 * Moves pet to the given coordinates.
	 */
	protected function moveTo (targetPos :Array) :void
	{
		if (targetPos != null && _ownerId != 0) {
			var myPos :Array = _ctrl.getPixelLocation();
			var distance :Number = Math.sqrt(Math.pow((targetPos[2] - myPos[2]), 2) + Math.pow((targetPos[0] - myPos[0]), 2));
			if (distance > _dist) {
				var vector :Number = Math.atan2(targetPos[2] - myPos[2], targetPos[0] - myPos[0]) + Math.PI;
				_ctrl.setPixelLocation(targetPos[0] + Math.cos(vector) * _dist, targetPos[1], targetPos[2] + Math.sin(vector) * _dist, (targetPos[0] < myPos[0] ? 270 : 90));
			}
		}
	}

    protected var _ctrl :PetControl;
    protected var _body :Body;
    protected var _rando :Random = new Random();

    protected var _state :State;
    protected var _states :Map = Maps.newMapOf(String);
	
	protected var _ownerId :int;
	protected var _dist :Number;
}
}
